// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © LuxAlgo

//@version=5
indicator('Birdies [LuxAlgo]', shorttitle='LuxAlgo - Birdies', max_lines_count = 500, max_labels_count = 500, max_polylines_count = 100, calc_bars_count = 5000, overlay=true)

//---------------------------------------------------------------------------------------------------------------------}
// Settings
//---------------------------------------------------------------------------------------------------------------------{
showBirdy    = input.string('Birdy'  , 'Birdy'              , display = display.all - display.status_line
             , options     =         [ 'Birdy', 'Birdy + Top', 'Left Wing', 'None']                                                                     ) 
left         = input.int   ( 100     , 'Swing Length'       , display = display.all - display.status_line                                                )   
minX         = input.int   (  50     , 'Minimum X-Distance' , display = display.all - display.status_line, group='Validation', minval=1, maxval=300
             , tooltip     =           'Minimum required amount of bars\nfor next Right Swing Point'                                                       )
minY         = input.float (   3     , 'Minimum Y-Distance' , display = display.all - display.status_line, group='Validation', minval=0, maxval= 10, step=0.1
             , tooltip     =           'Minimum required "Multiple of ATR"\nfor next Right Swing Point'                                                      )
xtra         = input.float (   0, 'Buffer (Multiple of ATR)', display = display.all - display.status_line, group='Validation', minval=0, step=0.01            )
bull         = input.bool  (true     ,      ''              , display = display.all - display.status_line, group='Style', inline='bull'                        )
cBull        = input.color (#089981, 'Bullish Patterns  ' , display = display.all - display.status_line, group='Style', inline='bull'                         )
bear         = input.bool  (true     ,      ''              , display = display.all - display.status_line, group='Style', inline='bear'                          ) 
cBear        = input.color (#f23645, 'Bearish Patterns'   , display = display.all - display.status_line, group='Style', inline='bear'                           )
sBuffer      = input.bool  (false    , 'Buffer Zone        ', display = display.all - display.status_line, group='Style', inline='buffer'                          )
cBuffer      = input.color (#b2b5be6a,    ''              , display = display.all - display.status_line, group='Style', inline='buffer'                           )
showFL       = input.bool  (true     , 'Color Fill'         , display = display.all - display.status_line, group='Style'                                             )
showOrigine  = input.bool  (false    , 'Origine'            , display = display.all - display.status_line, group='Style'                                              )
lSize        = str.lower(                                                                                              
               input.string('Tiny'   ,     'Dot Size'       , display = display.all - display.status_line, group='Style', options=['Tiny', 'Small', 'Normal', 'Large']) ) 

//---------------------------------------------------------------------------------------------------------------------}
//LuxAlgo Defined Types
//---------------------------------------------------------------------------------------------------------------------{
type cp 
    array<chart.point> cp 
    chart.point lastSwing
    float atr

type bin 
    float val 
    float atr 

//---------------------------------------------------------------------------------------------------------------------}
//Variables
//---------------------------------------------------------------------------------------------------------------------{
var mp      = map.new<int, bin>() 
var aCoords = array.new<cp>()
var color cTop = na 
var color cBot = na
var float lo   = na 
var float hi   = na 
n              = bar_index
s2             = math.sqrt(2)

//---------------------------------------------------------------------------------------------------------------------}
//Methods
//---------------------------------------------------------------------------------------------------------------------{
method loop(int idx) =>
    float val = na 
    get = aCoords.get(idx).cp
    for i = 0 to get.size() -1 
        if get.get(i).index == n 
            val := get.get(i).price
            break
    val

//---------------------------------------------------------------------------------------------------------------------}
//Exec
//---------------------------------------------------------------------------------------------------------------------{
//                       
//                  •   • 
//             •             •  O2         
//          •                •  •
//        •              •        • 
//       •            O            •
//       •     √2 •   |            •    √2(1 + 1) = √2
//        •    •      |1          • 
//          • ________|         •
//          A  • 1     B     •            
//                  •   • 
//
// When AB == 1 unit
// and  BO == 1 unit
// then AO == √2 -> radius / diameter would be 2*√2

ph          = ta.pivothigh(left, 1)
vwPh_bix    = ta.valuewhen(not na(ph), n-1, 0)
vwPh_prc    = ta.valuewhen(not na(ph), ph , 0)

pl          = ta.pivotlow (left, 1)
vwPl_bix    = ta.valuewhen(not na(pl), n-1, 0)
vwPl_prc    = ta.valuewhen(not na(pl), pl , 0)

atr         = ta.atr(14)
atrY        = atr * minY

if bull and not na(ph) and ph > vwPl_prc and n - vwPl_bix > 3

    valid = true
    sz = aCoords.size()
    if sz > 0 
        get0 = aCoords.first().lastSwing
        if n-1 - get0.index < minX or math.abs(ph - get0.price) < atrY
            valid := false

    if valid
        int maxX = 0, lastY = ph,lastX = n-2
        swingL_x = vwPl_bix
        swingL_y = vwPl_prc
        swingH_x = n-1
        swingH_y = ph
        center_x = int(math.avg(swingL_x, n-1))
        center_y = math.avg    (ph, swingL_y)
        coord    = array.new<chart.point>()        
        active   = false

        coord.push(chart.point.from_index(swingH_x, swingH_y))

        for i = 0 to 360

            Xcoord = (center_x - swingL_x) * s2 * math.sin(math.toradians(i))
            Ycoord = (center_y - swingL_y) * s2 * math.cos(math.toradians(i))
            x      =  math.round(center_x  + Xcoord)
            y      =             center_y  + Ycoord
            maxX  :=  math.max  (maxX      , x)

            if not active and x > swingH_x 
                active := true 
            if  active
                if         x != lastX 
                    diff = x  - lastX
                    if diff    > 1
                        for j  = 1 to diff -1
                            if lastX != n-3
                                coord.push(chart.point.from_index(lastX + j, lastX + j == n-1 ? ph : lastY - ((lastY  - y) / diff * j ))) 
                    lastY := y
                    lastX := x
                    //x too far error handling
                    if x - n < 500
                        coord.push(chart.point.from_index(x, y))

                if y <= center_y
                    active := false 
                    break

        //Right Wing
        if showBirdy != 'Left Wing'  
            for i = coord.size() -1 to 0 
                get = coord.get(i)
                //x too far error handling
                if get.index + 2 * (maxX - get.index) - n < 500
                    coord.push(chart.point.from_index(get.index + 2 * (maxX - get.index), get.price))

        if showBirdy != 'None'
            polyline.new(coord, line_color=cBull)
            if showBirdy == 'Birdy + Top'
                line.new(coord.first(), coord.last(), color=cBull)

        //Connection Line 2 Swings
        if showOrigine
            line.new(swingL_x, swingL_y, swingH_x, swingH_y, color=color.new(cBull, 65))

        aCoords.unshift(cp.new(coord, chart.point.from_index(n-1, ph), atr))

if bear and not na(pl) and pl < vwPh_prc and n - vwPh_bix > 3

    valid = true
    sz = aCoords.size()
    if sz > 0
        get0 = aCoords.first().lastSwing
        if n-1 - get0.index < minX or math.abs(pl - get0.price) < atrY
            valid := false

    if valid
        int maxX = 0, lastY = pl,lastX = n-2
        swingH_x = vwPh_bix
        swingH_y = vwPh_prc
        swingL_x = n-1
        swingL_y = pl
        center_x = int(math.avg(swingH_x, n-1))
        center_y = math.avg    (pl, swingH_y)
        coord    = array.new<chart.point>()
        active   = false

        coord.push(chart.point.from_index(swingL_x, swingL_y))

        for i = 0 to 360

            Xcoord = (swingH_x - center_x) * s2 * math.sin(math.toradians(i))
            Ycoord = (swingH_y - center_y) * s2 * math.cos(math.toradians(i))
            x     = math.round  (center_x  + Xcoord)
            y     =              center_y  + Ycoord
            maxX  := math.max   (maxX      , x)

            if not active and x > swingL_x
                active := true 
            if  active
                if         x != lastX 
                    diff = x  - lastX
                    if diff    > 1
                        for j  = 1 to diff -1
                            if lastX != n-3                                    
                                coord.push(chart.point.from_index(lastX + j , lastX + j == n-1 ? pl : lastY + ((y - lastY) / diff * j))) 
                    lastY := y
                    lastX := x      
                    //x too far error handling
                    if x - n < 500
                        coord.push(chart.point.from_index(x, y))

                if y >= center_y
                    active := false 
                    break

        //Right Wing
        if showBirdy != 'Left Wing'  
            for i = coord.size() -1 to 0 
                get = coord.get(i)
                //x too far error handling
                if get.index + 2 * (maxX - get.index) - n < 500
                    coord.push(chart.point.from_index(get.index + 2 * (maxX - get.index), get.price))

        if showBirdy != 'None'
            polyline.new(coord, line_color=cBear)
            if showBirdy == 'Birdy + Top'
                line.new(coord.first(), coord.last(), color=cBear)

        //Connection Line 2 Swings
        if showOrigine
            line.new(swingL_x, swingL_y, swingH_x, swingH_y, color=color.new(cBear, 65))

        aCoords.unshift(cp.new(coord, chart.point.from_index(n-1, pl), atr))

sz = aCoords.size()
if sz > 1 
    
    float _1 = na    
    float _2 = na

    for i = 0 to sz -1
        loopy = i.loop()
        if not na(loopy)
            _1 := loopy
            if sz > i +1
                for j = i +1 to sz -1
                    loopy := j.loop()
                    if not na(loopy)
                        _2 := loopy
                        break
            break

    lo := math.min(_1, _2)
    hi := math.max(_1, _2)

sz := aCoords.size()
if sz > 0
    for k = sz -1 to 0
        coord = aCoords.get(k)
        get   = coord.cp
        if get.size() > 0
            if get.last().index < n - minX
                aCoords.remove(k)
            if get.last().index < n
                mp.remove(coord.lastSwing.index)
            else
                for i = 0 to get.size() -1 
                    if get.get(i).index == n 
                        val = get.get(i).price
                        mp.put(coord.lastSwing.index, bin.new(val, coord.atr))
                        if i > 0 
                            val1 = get.get(i-1).price
                            if close > val + coord.atr * xtra
                                if close[1] < val1 //or open < val
                                    label.new(n, low , text="●", color=color(na)
                                     , style=label.style_label_up  , textcolor=cBull, size=lSize)
                            if close < val - coord.atr * xtra
                                if close[1] > val1 //or open > val
                                    label.new(n, high, text="●", color=color(na)
                                     , style=label.style_label_down, textcolor=cBear, size=lSize)
                        break

//---------------------------------------------------------------------------------------------------------------------}
//Plot
//---------------------------------------------------------------------------------------------------------------------{
pLo = plot(lo, color =color(na), display=display.pane)
pHi = plot(hi, color =color(na), display=display.pane)

cTop := not showFL ? na : close > hi ? color    (   na    ) : close < lo ? color.new(cBear, 80) : cTop
cBot := not showFL ? na : close > hi ? color.new(cBull, 80) : close < lo ? color    (  na     ) : cBot

fill(pHi, pLo, hi, lo, cTop, cBot) 

mp.keys().sort()

get0 = mp.size() > 0 ? mp.get(mp.keys().get(0)) : bin.new(na, na), ch0 = ta.change(get0.atr)
get1 = mp.size() > 1 ? mp.get(mp.keys().get(1)) : bin.new(na, na), ch1 = ta.change(get1.atr)
get2 = mp.size() > 2 ? mp.get(mp.keys().get(2)) : bin.new(na, na), ch2 = ta.change(get2.atr)
get3 = mp.size() > 3 ? mp.get(mp.keys().get(3)) : bin.new(na, na), ch3 = ta.change(get3.atr)
get4 = mp.size() > 4 ? mp.get(mp.keys().get(4)) : bin.new(na, na), ch4 = ta.change(get4.atr)
get5 = mp.size() > 5 ? mp.get(mp.keys().get(5)) : bin.new(na, na), ch5 = ta.change(get5.atr)
get6 = mp.size() > 6 ? mp.get(mp.keys().get(6)) : bin.new(na, na), ch6 = ta.change(get6.atr)
get7 = mp.size() > 7 ? mp.get(mp.keys().get(7)) : bin.new(na, na), ch7 = ta.change(get7.atr)
get8 = mp.size() > 8 ? mp.get(mp.keys().get(8)) : bin.new(na, na), ch8 = ta.change(get8.atr)
get9 = mp.size() > 9 ? mp.get(mp.keys().get(9)) : bin.new(na, na), ch9 = ta.change(get9.atr)

plot(sBuffer and ch0 == 0 ? get0.val + get0.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch1 == 0 ? get1.val + get1.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch2 == 0 ? get2.val + get2.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch3 == 0 ? get3.val + get3.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch4 == 0 ? get4.val + get4.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch5 == 0 ? get5.val + get5.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch6 == 0 ? get6.val + get6.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch7 == 0 ? get7.val + get7.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch8 == 0 ? get8.val + get8.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch9 == 0 ? get9.val + get9.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch0 == 0 ? get0.val - get0.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch1 == 0 ? get1.val - get1.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch2 == 0 ? get2.val - get2.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch3 == 0 ? get3.val - get3.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch4 == 0 ? get4.val - get4.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch5 == 0 ? get5.val - get5.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch6 == 0 ? get6.val - get6.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch7 == 0 ? get7.val - get7.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch8 == 0 ? get8.val - get8.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)
plot(sBuffer and ch9 == 0 ? get9.val - get9.atr * xtra : na, style=plot.style_linebr, color=cBuffer, display=display.all - display.status_line)

//---------------------------------------------------------------------------------------------------------------------}